﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

namespace HossMvp.Core
{
    public abstract class CommandProcessor : ICommandProcessor
    {
        internal class Command : ICommand
        {
            private bool enabled = true;
            private readonly ICollection<dynamic> controls;
            private readonly CommandProcessor commandProcessor;

            public Command(CommandProcessor commandProcessor, string name, Action<CommandContext> execute, Func<bool> canExecute)
            {
                this.commandProcessor = commandProcessor;
                this.Name = name;
                this.Execute = execute;
                this.CanExecute = canExecute;
                this.controls = new List<dynamic>();
            }

            public string Name { get; private set; }

            public virtual ICommand Bind(dynamic control, int eventType)
            {
                this.commandProcessor.RouteCommand(control, eventType, this.Name);
                return this;
            }

            // ReSharper disable once ParameterHidesMember
            public virtual ICommand Bind(IEnumerable<dynamic> controls, int eventType)
            {
                foreach (var control in controls)
                {
                    this.Bind(control, eventType);
                }

                return this;
            }

            public virtual Action<CommandContext> Execute { get; private set; }

            public Func<bool> CanExecute { get; private set; }

            public bool Enabled
            {
                get
                {
                    var canExecute = this.CanExecute();
                    if (this.enabled != canExecute)
                    {
                        this.enabled = canExecute;
                        foreach (var control in this.controls)
                        {
                            var property = control.GetType().GetProperty("Enabled");
                            property.SetValue(control, this.enabled, null);
                        }
                    }

                    return this.enabled;
                }
            }

            internal void AddControl(dynamic control)
            {
                this.controls.Add(control);
            }

            public override string ToString()
            {
                // helps with debugging
                return string.Format("{0}, {1}", this.Name, this.GetType().Name);
            }
        }

        internal class DecoratedCommand : Command
        {
            public DecoratedCommand(CommandProcessor commandProcessor, string name, Action<CommandContext> execute, Func<bool> canExecute, Func<bool> preExecute)
                : base(commandProcessor, name, execute, canExecute)
            {
                this.PreExecute = preExecute;
            }

            public Func<bool> PreExecute { get; set; }

            public override Action<CommandContext> Execute
            {
                get
                {
                    return arg =>
                    {
                        if (this.PreExecute())
                        {
                            Debug.Assert(base.Execute != null, "base.Execute != null");
                            base.Execute(arg);
                        }
                    };
                }
            }
        }

        private readonly IEnumerable<ICommand> commands;

        public NameDecorator Decorator { get; private set; }

        protected CommandProcessor(NameDecorator decorator)
        {
            this.Decorator = decorator;
            this.commands = new List<ICommand>();
        }

        public virtual ICommand RegisterCommand(string name, Action<CommandContext> execute, Func<bool> canExecute)
        {
            if (name == null) { throw new ArgumentNullException("name"); }
            if (execute == null) { throw new ArgumentNullException("execute"); }
            if (canExecute == null) { throw new ArgumentNullException("canExecute"); }

            name = this.Decorator.Decorate(name);
            if (this.commands.SingleOrDefault(c => c.Name == name) != null)
            {
                throw new InvalidOperationException("Command already exists");
            }

            var command = new Command(this, name, execute, canExecute);
            ((ICollection<ICommand>)this.commands).Add(command);

            return command;
        }

        public virtual ICommand RegisterCommand(string name, Action<CommandContext> execute, Func<bool> canExecute, Func<bool> preExecute)
        {
            if (name == null) { throw new ArgumentNullException("name"); }
            if (execute == null) { throw new ArgumentNullException("execute"); }
            if (canExecute == null) { throw new ArgumentNullException("canExecute"); }
            if (preExecute == null) { throw new ArgumentNullException("preExecute"); }

            name = this.Decorator.Decorate(name);
            if (this.commands.SingleOrDefault(c => c.Name == name) != null)
            {
                throw new InvalidOperationException("Command already exists");
            }

            var command = new DecoratedCommand(this, name, execute, canExecute, preExecute);
            ((ICollection<ICommand>)this.commands).Add(command);

            return command;
        }

        public ICommand GetCommand(string name)
        {
            if (name == null) { throw new ArgumentNullException("name"); }

            name = this.Decorator.Decorate(name);
            var command = this.commands.First(c => c.Name == name);
            return command;
        }

        public virtual void UnregisterCommand(string name)
        {
            if (name == null) { throw new ArgumentNullException("name"); }

            name = this.Decorator.Decorate(name);
            if (this.commands.SingleOrDefault(c => c.Name == name) == null)
            {
                throw new InvalidOperationException("Command does not exist, cannot remove.");
            }
        }

        public virtual void Enable()
        {
            Func<ICommand, object> checkEnabled = command => command.Enabled;
            foreach (var command in this.commands)
            {
                checkEnabled(command);
            }
        }

        public virtual ICommand RouteCommand(object ctrl, int eventType, string name)
        {
            if (ctrl == null) { throw new ArgumentNullException("ctrl"); }
            if (name == null) { throw new ArgumentNullException("name"); }

            dynamic control = ctrl;

            name = this.Decorator.Decorate(name);
            var command = this.commands.Single(c => c.Name == name);
            ((Command)command).AddControl(control);
            switch (eventType)
            {
                case EventTypeConstants.Close:
                    control.Close += this.MakeHandler(command, eventType);
                    break;

                case EventTypeConstants.Click:
                    control.Click += this.MakeHandler(command, eventType);
                    break;

                case EventTypeConstants.Closing:
                    control.Closing += this.MakeHandler(command, eventType);
                    break;

                default:
                    return command; // continue routing in descendents
            }

            return null;
        }

        protected abstract dynamic MakeHandler(ICommand command, int eventType);
    }
}